#include "baseDB.h"
#include "tcl_wrapper.h"
#include <vector>
#include <math.h>

using namespace std;

extern vector<unsigned int> V_CHANGEs;
extern vector<C_CELL*> V_CELLs; // netlist cell
extern vector< vector< vector<int> > > THRD_GRID;
extern unsigned int XDIM,YDIM,ZDIM;

float fMX,fMY,fMZ,fmx,fmy,fmz; // MAX X,Y,Z and min x,y,z, force-directed boundary

// private function declaration
bool _placeCells (unsigned int iteration);
void _calc_pushing_force (unsigned int cellIdx,float& x,float& y,float& z);
void _calc_pulling_force (unsigned int cellIdx,float weight,float& x,float& y,float& z);
void _update_boundary(const float& x,const float& y,const float& z);
void _scale_reslut_to_cube_size (void);

int fd_place (ClientData clientData,
                Tcl_Interp *interp,
                int argc, const char *argv[]) {
 
    int iteration;
    if (Tcl_GetInt(interp,argv[1],&iteration) != TCL_OK) {
        printf("Error: can not get iteration %s\n",argv[1]);
        return TCL_ERROR;
    }

    //init
    fMX = 0.0;
    fMY = 0.0;
    fMZ = 0.0;
    fmx = 999999.9;
    fmy = 999999.9;
    fmz = 999999.9;

    _placeCells((unsigned int)iteration);
    return TCL_OK;
}

int fd_compress (ClientData clientData,
                Tcl_Interp *interp,
                int argc, const char *argv[]) {
    
    _scale_reslut_to_cube_size();
    return TCL_OK;
}

// private
bool _placeCells (unsigned int iteration) {
    
    float x,y,z;
    float vx,vy,vz;
    float pushx,pushy,pushz;
    float pullx,pully,pullz;

    V_CHANGEs.clear();

    for (unsigned int i=0;i<iteration;i++) {
        printf("iter # %d\n",i);
        for (unsigned int j=0;j<V_CELLs.size();j++) {
            
            V_CELLs[j]->getLoc(x,y,z);
            //printf("curr cell (%d,%d,%d)\n",x,y,z);
            if (V_CELLs[j]->getType() == e_clb) {
                _calc_pushing_force (j,pushx,pushy,pushz);
                _calc_pulling_force (j,10.0,pullx,pully,pullz);
                x += pushx-pullx;
                y += pushy-pully;
                z += pushz-pullz;

                _update_boundary(x,y,z);
                
                V_CELLs[j]->setLoc(x,y,z);
                //V_CHANGEs.push_back(j);
                //printf("pushing force (%f,%f,%f)\n",pushx,pushy,pushz);
                //printf("pulling force (%f,%f,%f)\n",pullx,pully,pullz);
                //printf("direction (%f,%f,%f)\n",pushx-pullx,pushy-pully,pushz-pullz);
                
                //return true;
            } else {
                
            }

        }

    }
    printf("Boundary: (%f %f %f) (%f,%f,%f)\n",fMX,fMY,fMZ,fmx,fmy,fmz);
}

void _calc_pushing_force (unsigned int cellIdx,float& x,float& y,float& z) {
    
    int cx,cy,cz; // candidate coordinate
    int px,py,pz; // pushing force coordinate
    int dx,dy,dz;
    float vecx = 0.0;
    float vecy = 0.0 ,vecz = 0.0;
    float l;

    V_CELLs[cellIdx]->getLoc(cx,cy,cz);

    // This can be speeded up by checking neighbor points only
    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        V_CELLs[i]->getLoc(px,py,pz);
        
        dx = px-cx;
        dy = py-cy;
        dz = pz-cz;
        
        l = 2.0*(dx*dx+dy*dy+dz*dz);
        
        if (l==0.0)
            continue;
        
        vecx += dx/l;
        vecy += dy/l;
        vecz += dz/l;
        //printf("%f %d %d %d %d %d %d %10.10f %f %f\n",l,px,py,pz,cx,cy,cz,vecx,vecy,vecz);
    }

    x = vecx;
    y = vecy;
    z = vecz;
}

void _calc_pulling_force (unsigned int cellIdx,float weight,float& x,float& y,float& z) {
    
    vector<unsigned int> edges;
    int cx,cy,cz; // candidate coordinate
    int px,py,pz; // pulling force coordinate
    int dx,dy,dz;
    float vecx = 0.0,vecy = 0.0 ,vecz = 0.0;
    int edgeCount = 0;
    float l;

    V_CELLs[cellIdx]->getLoc(cx,cy,cz);
    
    // calc fanout
    V_CELLs[cellIdx]->getFanout(edges);
    
    edgeCount += edges.size();

    for (unsigned int i=0;i<edges.size();i++) {
        
        V_CELLs[i]->getLoc(px,py,pz);
        
        dx = px-cx;
        dy = py-cy;
        dz = pz-cz;

        vecx -= dx;
        vecy -= dy;
        vecz -= dz;
        //printf("d(%d,%d,%d)\n",dx,dy,dz);
    }

    // calc fanin
    edges.clear();
    V_CELLs[cellIdx]->getFanin(edges);
    edgeCount += edges.size();

    for (unsigned int i=0;i<edges.size();i++) {
        
        V_CELLs[i]->getLoc(px,py,pz);
        
        dx = px-cx;
        dy = py-cy;
        dz = pz-cz;

        vecx -= dx;
        vecy -= dy;
        vecz -= dz;
    }
    //printf("edge count=%d\n",edgeCount);
    x = vecx/edgeCount;
    y = vecy/edgeCount;
    z = vecz/edgeCount;
}

void _update_boundary(const float& x,const float& y,const float& z) {
    
    if (x>fMX)
        fMX = x;
    if (x<fmx)
        fmx = x;
    if (y>fMY)
        fMY = y;
    if (y<fmy)
        fmy = y;
    if (z>fMZ)
        fMZ = z;
    if (z<fmz)
        fmz = z;
}

void _scale_reslut_to_cube_size (void) {
 
    float x,y,z;
    V_CHANGEs.clear();

    // shift by fmx,fmy,fmz
    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        if (V_CELLs[i]->getType() == e_clb) {
            
            V_CELLs[i]->getLoc(x,y,z);
            
            if (fmx<0.0)
                x -= fmx;
            if (fmy<0.0)
                y -= fmy;
            if (fmz<0.0)
                z -= fmz;
            
            V_CELLs[i]->setLoc(x,y,z);
        }

    }

    // shift MX,MY,MZ
    if (fmx<0.0)
        fMX -= fmx;
    if (fmy<0.0)
        fMY -= fmy;
    if (fmz<0.0)
        fMZ -= fmz;

    // scale to cube boundary
    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        if (V_CELLs[i]->getType() == e_clb) {
            
            V_CELLs[i]->getLoc(x,y,z);
            
            x = rint(x*((float)XDIM)/fMX);
            y = rint(y*((float)YDIM)/fMY);
            z = rint(z*((float)ZDIM)/fMZ);
            //printf("scale to new loc: %f,%f,%f\n",x,y,z);
            V_CELLs[i]->setLoc(x,y,z);
            V_CHANGEs.push_back(i);
        }

    }
}
